package com.huawei.translate.service;

import com.huawei.translate.model.AccessToken;
import com.huawei.translate.model.Renter;
import com.huawei.translate.persistence.AccessTokenRepository;
import com.huawei.translate.persistence.RenterRepository;
import com.huawei.translate.utils.Encryptor;
import com.huawei.translate.utils.SystemConfig;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import java.util.List;

@Service
public class AuthenticationService {

    @Autowired
    private Encryptor encryptor;

    @PersistenceContext
    private EntityManager em;

    @Autowired
    private AccessTokenRepository accessTokenRepository;

    @Autowired
    private RenterRepository renterRepository;

    public static final int APP_SECRET_LENGTH = 36;
    public static final int TIME_STAMP_LENGTH = 13;

    public String generateAccessToken(String appSecret, String salt) {

        long applyTime = System.currentTimeMillis();
        AccessToken token = new AccessToken(appSecret, salt, applyTime);
        accessTokenRepository.save(token);
        return encryptor.base64TryEncode(StringUtils.join(new String[]{appSecret, salt, String.valueOf(applyTime)}));
    }

    public AccessToken parseAccessToken(String accessToken) {
        String decodeStr = encryptor.base64TryDecode(accessToken);

        return new AccessToken(extractAppSecret(decodeStr), extractSalt(decodeStr), extractApplyTime(decodeStr));
    }

    public Renter parseRenterFromAccessToken(String accessToken) {
        String appSecret = extractAppSecret(encryptor.base64TryDecode(accessToken));

        return renterRepository.findByAppSecret(appSecret);
    }

    private String extractSalt(String decodeStr) {
        if (decodeStr == null || decodeStr.length() < (TIME_STAMP_LENGTH + APP_SECRET_LENGTH)) {
            return null;
        }
        return decodeStr.substring(APP_SECRET_LENGTH, decodeStr.length() - TIME_STAMP_LENGTH);
    }

    private long extractApplyTime(String decodeStr) {

        if (decodeStr == null || decodeStr.length() < TIME_STAMP_LENGTH) {
            return 0;
        }

        try {
            return Long.parseUnsignedLong(decodeStr.substring(decodeStr.length() - TIME_STAMP_LENGTH));
        } catch (NumberFormatException e) {
            return 0;
        }
    }

    public String extractAppSecret(String decodeStr) {
        if (decodeStr == null || decodeStr.length() < APP_SECRET_LENGTH) {
            return null;
        }
        return decodeStr.substring(0, APP_SECRET_LENGTH);
    }

    public boolean isUserValid(String accessToken) {

        AccessToken token = parseAccessToken(accessToken);

        List<AccessToken> tokens = accessTokenRepository.findByAppSecretAndSaltAndApplyTime(token.getAppSecret(), token.getSalt(), token.getApplyTime());

        if (CollectionUtils.isEmpty(tokens) || isExpired(tokens.get(0))) {
            return false;
        }

        String validationStr = encryptor.base64TryDecode(accessToken);
        if (!validationStr.contains(StringUtils.join(new String[]{tokens.get(0).getAppSecret(), tokens.get(0).getSalt()}))) {
            return false;
        }
        return true;
    }

    private boolean isExpired(AccessToken token) {
        long expiredDuration = Long.parseLong(SystemConfig.getProperty("expiredDuration", "300000"));
        return (System.currentTimeMillis() - expiredDuration) > token.getApplyTime();
    }

    @Transactional
    public void cleanExpiredToken(long expiredDuration) {
        String sql = "Delete from token where apply_time<=" + expiredDuration;
        em.createNativeQuery(sql).executeUpdate();
    }
}
